package com.burukeyou.smartdi.proxyspi;

import com.burukeyou.smartdi.config.SpringBeanContext;
import com.burukeyou.smartdi.exceptions.SPIBeanTypeMismatchException;
import com.burukeyou.smartdi.proxyspi.spi.ProxySPI;
import com.burukeyou.smartdi.proxyspi.factory.AnnotationProxyFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;

import java.lang.annotation.Annotation;

/**
 * @author caizhihao
 */
public class ProxySpiLoader {

    private ProxySpiLoader(){}

    /**
     * 根据ProxySPI以及其复合注解的类指定的代理工厂去得到代理对象
     * @param clz
     * @param <T>
     * @return
     */
    public static <T> T load(Class<T> clz){
        /* 用于获取某个元素（如类、方法或字段）上指定类型的合并注解
            支持注解的继承：不仅扫描目标元素上的直接注解，还会递归查找元注解（即注解上的注解）。
            合并属性值：当目标注解和元注解有属性值时，会进行智能合并，优先选择更具体的注解属性值。
            多层次处理：支持多层次的元注解解析
         */
        ProxySPI proxySPI = AnnotatedElementUtils.getMergedAnnotation(clz, ProxySPI.class);
        // 判断是否有ProxySPI注解
        Annotation obj = getSPIObject(clz);
        if (obj == null){
            throw new RuntimeException(clz.getSimpleName() + "请标记ProxySPI的注解或者复合注解");
        }
        // 获取注解上的代理对象工厂
        Class<? extends AnnotationProxyFactory<?>> proxyFactory = proxySPI.value();
        // 获取工厂对象
        AnnotationProxyFactory<Annotation> springBean = (AnnotationProxyFactory<Annotation>) SpringBeanContext.getBean(proxyFactory);
        // 通过工程对象产生实例对象
        Object proxy = springBean.getProxy(clz, obj);
        if (proxy == null){
            return null;
        }

        if (!clz.isAssignableFrom(proxy.getClass())){
            throw new SPIBeanTypeMismatchException(springBean.getClass().getSimpleName() + " get the bean type  is "+proxy.getClass().getSimpleName()+", need inject class type " + clz.getSimpleName());
        }

        return (T)proxy;
    }


    /**
     * 获取需要注入的类是否是加了ProxySPI的注解或者复合注解
     * @param clz
     * @return
     */
    public  static Annotation getSPIObject(Class<?> clz){
        Annotation[] annotations = clz.getAnnotations();
        for (Annotation element : annotations) {
            Class<? extends Annotation> aClass = element.annotationType();
            if(aClass == ProxySPI.class || aClass.isAnnotationPresent(ProxySPI.class)){
                return element;
            }
        }

       return null;
    }
}
